#include "network_interface.hh"

#include "arp_message.hh"
#include "ethernet_frame.hh"

#include <iostream>

// Dummy implementation of a network interface
// Translates from {IP datagram, next hop address} to link-layer frame, and from link-layer frame to IP datagram

// For Lab 5, please replace with a real implementation that passes the
// automated checks run by `make check_lab5`.

// You will need to add private members to the class declaration in `network_interface.hh`

template <typename... Targs>
void DUMMY_CODE(Targs &&... /* unused */) {}

using namespace std;

//! \param[in] ethernet_address Ethernet (what ARP calls "hardware") address of the interface
//! \param[in] ip_address IP (what ARP calls "protocol") address of the interface
NetworkInterface::NetworkInterface(const EthernetAddress &ethernet_address, const Address &ip_address)
    : _ethernet_address(ethernet_address), _ip_address(ip_address) {
    cerr << "DEBUG: Network interface has Ethernet address " << to_string(_ethernet_address) << " and IP address "
         << ip_address.ip() << "\n";
}

//! \param[in] dgram the IPv4 datagram to be sent
//! \param[in] next_hop the IP address of the interface to send it to (typically a router or default gateway, but may also be another host if directly connected to the same network as the destination)
//! (Note: the Address type can be converted to a uint32_t (raw 32-bit IP address) with the Address::ipv4_numeric() method.)
void NetworkInterface::send_datagram(const InternetDatagram &dgram, const Address &next_hop) {
    // convert IP address of next hop to raw 32-bit representation (used in ARP header)
    const uint32_t next_hop_ip = next_hop.ipv4_numeric();
    EthernetFrame &&ethernetFrame{};
    EthernetHeader &ethernetHeader = ethernetFrame.header();
    bool isArpCacheExpired = false;
    if(_arp_cache.find(next_hop_ip) != _arp_cache.end() and not (isArpCacheExpired = _current_time - _arp_cache[next_hop_ip].first > 30000)){
        // 有cache
        ethernetHeader.type = EthernetHeader::TYPE_IPv4;
        ethernetHeader.dst = _arp_cache[next_hop_ip].second;
        ethernetHeader.src = _ethernet_address;
        ethernetFrame.payload() = dgram.serialize();
    }else if(_ip_wait_for_ether.find(next_hop_ip) == _ip_wait_for_ether.end()
               or _current_time - _ip_wait_for_ether[next_hop_ip].first > 5000){
        // 需要发arp request，如果之前没请求过，或者之前的请求已超过5s，便可以发

        // 如果arp缓存过期，将其删除（可以不用删除，等收到reply，可以直接将其覆盖）
//        if(isArpCacheExpired)
//            _arp_cache.erase(next_hop_ip);

        ethernetHeader.type = EthernetHeader::TYPE_ARP;
        ethernetHeader.dst = ETHERNET_BROADCAST;
        ethernetHeader.src = _ethernet_address;

        ARPMessage &&arpMessage{};
        arpMessage.opcode = ARPMessage::OPCODE_REQUEST;
        arpMessage.sender_ethernet_address = _ethernet_address;
        arpMessage.sender_ip_address = _ip_address.ipv4_numeric();
        arpMessage.target_ethernet_address = array<uint8_t, 6>{0};
        arpMessage.target_ip_address = next_hop_ip;
        ethernetFrame.payload() = arpMessage.serialize();

        _ip_wait_for_ether[next_hop_ip].first = _current_time;
        _ip_wait_for_ether[next_hop_ip].second.emplace(dgram);
    }else
        return;

    _frames_out.push(move(ethernetFrame));
}

//! \param[in] frame the incoming Ethernet frame
optional<InternetDatagram> NetworkInterface::recv_frame(const EthernetFrame &frame) {
    if(frame.header().dst != _ethernet_address and frame.header().dst != ETHERNET_BROADCAST)
        return nullopt;

    Buffer buffer;
    try {
        buffer = frame.payload();
    } catch (runtime_error &e) {
        cerr << "recv_frame: buffer list failed to be parsed, err is " << e.what() << endl;
    }

    if(frame.header().type == EthernetHeader::TYPE_IPv4){
        // IPv4
        InternetDatagram &&datagram{};
        ParseResult &&res = datagram.parse(buffer);
        if (res == ParseResult::NoError) {
            _arp_cache[datagram.header().src].first = _current_time;
            _arp_cache[datagram.header().src].second = frame.header().src;
            return move(datagram);
        }
    }else if(frame.header().type == EthernetHeader::TYPE_ARP){
        // ARP
        ARPMessage &&arpMessage{};
        ParseResult &&res = arpMessage.parse(buffer);
        if(res == ParseResult::NoError and arpMessage.target_ip_address == _ip_address.ipv4_numeric()){
            auto &dst_ip = arpMessage.sender_ip_address;
            auto &dst_ether = arpMessage.sender_ethernet_address;
            switch (arpMessage.opcode) {
                case ARPMessage::OPCODE_REPLY: {
                    _arp_cache[dst_ip].first = _current_time;
                    _arp_cache[dst_ip].second = dst_ether;
                    auto &ip_queue = _ip_wait_for_ether[dst_ip].second;
                    EthernetFrame &&ethernetFrame{};
                    ethernetFrame.header().src = _ethernet_address;
                    ethernetFrame.header().dst = dst_ether;
                    ethernetFrame.header().type = EthernetHeader::TYPE_IPv4;
                    while(not ip_queue.empty()){
                        ethernetFrame.payload() = ip_queue.front().serialize();
                        _frames_out.emplace(ethernetFrame);
                        ip_queue.pop();
                    }
                    //_ip_wait_for_ether.erase(dst_ip);
                    break;
                }
                case ARPMessage::OPCODE_REQUEST: {
                    // 收到别的host广播的arp request，学习sender的ip与ether的匹配
                    _arp_cache[dst_ip].first = _current_time;
                    _arp_cache[dst_ip].second = dst_ether;
                    if(arpMessage.target_ip_address == _ip_address.ipv4_numeric()){
                        EthernetFrame &&ethernetFrame{};
                        EthernetHeader &ethernetHeader = ethernetFrame.header();
                        ethernetHeader.type = EthernetHeader::TYPE_ARP;
                        ethernetHeader.dst = dst_ether;
                        ethernetHeader.src = _ethernet_address;

                        ARPMessage &&arpReplyMessage{};
                        arpReplyMessage.opcode = ARPMessage::OPCODE_REPLY;
                        arpReplyMessage.sender_ethernet_address = _ethernet_address;
                        arpReplyMessage.sender_ip_address = _ip_address.ipv4_numeric();
                        arpReplyMessage.target_ethernet_address = dst_ether;
                        arpReplyMessage.target_ip_address = arpMessage.sender_ip_address;
                        ethernetFrame.payload() = arpReplyMessage.serialize();

                        _frames_out.push(move(ethernetFrame));
                    }
                }
            }
        }
    }

    return nullopt;
}

//! \param[in] ms_since_last_tick the number of milliseconds since the last call to this method
void NetworkInterface::tick(const size_t ms_since_last_tick) {
    _current_time += ms_since_last_tick;

}